package com.cloud.music.config.proxy;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSONObject;
import com.cloud.music.annotation.Music;
import com.cloud.music.annotation.MusicService;
import com.cloud.music.config.properties.MusicProperties;
import com.cloud.music.utils.CookieUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.http.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.client.RestTemplate;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;

@Slf4j
public class MusicServiceProxy<T> implements InvocationHandler, Serializable {

    private final Class<T> musicServiceInterface;

    public MusicServiceProxy(Class<T> musicServiceInterface) {
        this.musicServiceInterface = musicServiceInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        return getJson(proxy, method, args);
    }

    /**
     * 获取json对象
     *
     * @param proxy  代理对象
     * @param method 方法
     * @param args   参数
     * @return json对象
     */
    private JSONObject getJson(Object proxy, Method method, Object[] args) {
        JSONObject result = new JSONObject();
        if (musicServiceInterface.isAnnotationPresent(MusicService.class) && method.isAnnotationPresent(Music.class)) {
            // 读取类上注解
            MusicService musicServiceAnnotation = musicServiceInterface.getAnnotation(MusicService.class);
            // 读取方法上注解
            Music methodAnnotation = method.getAnnotation(Music.class);
            // 获取参数类型
            Class<?>[] parameterTypes = method.getParameterTypes();
            // 获取参数
            Parameter[] parameters = method.getParameters();
            // 转化原有对象类型
            for (int i = 0; i < parameters.length; i++) {
                // 如果参数类型不是基本类型，就转化为实际类型
                if (parameterTypes[i].isAssignableFrom(args[i].getClass())) {
                    args[i] =  parameterTypes[i].cast(args[i]);
                }
            }
            // 将参数转换成JSONObject
            JSONObject paramsJson;
            // 消除JSONObject中的null值
            JSONObject params = new JSONObject();
            if (ArrayUtil.isNotEmpty(args)) {
                // 判断是否是JSONObject类型
                if (args[0].getClass() == JSONObject.class){
                    paramsJson = (JSONObject) args[0];
                }else {
                    // 对有 @Validated 注解的参数对象进行校验
                    if (parameters[0].isAnnotationPresent(Validated.class)) {
                        validateParameter(args[0], parameterTypes[0]);
                    }
                    paramsJson = (JSONObject) JSONObject.toJSON(args[0]);
                }
                paramsJson.forEach((key, value) -> {
                    if (value != null) {
                        params.put(key, value);
                    }
                });
            }
            // 请求参数, 如果不传参为空对象
            String paramsStr = params.toString();
            // 拼接fullUrl
            String fullUrl = musicServiceAnnotation.serverAddress() + methodAnnotation.url();
            // 拼接时间戳
            long time = Timestamp.valueOf(LocalDateTime.now()).getTime();
            fullUrl += "?timestamp=" + time;
            // 设置请求头
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            // 从当前线程中获取cookie
            httpHeaders.add(HttpHeaders.COOKIE, CookieUtil.getCurrentCookie());

            HttpEntity<String> stringHttpEntity = new HttpEntity<String>(paramsStr, httpHeaders);
            // 获取spring容器中的MusicProperties对象
             MusicProperties musicProperties = SpringUtil.getBean(MusicProperties.class);
            // 判断是否开启日志
            if (musicProperties.getLog()) {
                log.info("Remote request information:" + musicServiceInterface.getName() + "." + method.getName() + ", request url:" + fullUrl + ", request type: POST" + ", request params:" + paramsStr);
            }
            List<String> setCookie = null;
            try {
                RestTemplate restTemplate = SpringUtil.getBean(RestTemplate.class);
                ResponseEntity<String> responseEntity = restTemplate.exchange(fullUrl, HttpMethod.POST, stringHttpEntity, String.class);
                result = JSONObject.parseObject(responseEntity.getBody());
                HttpHeaders headers = responseEntity.getHeaders();
                setCookie = headers.get("set-cookie");
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (setCookie != null) {
                // 更新cookie存入当前线程
                CookieUtil.setCurrentCookie(parseSetCookie(setCookie.toString()));
            }
        }
        return result;
    }

    /**
     * 解析set-cookie
     * @param setCookie set-cookie
     * @return cookie
     */
    private String parseSetCookie(String setCookie) {
        if (setCookie == null) {
            return "";
        }
        final String[] split = setCookie.split(";, ");
        final Optional<String> cookieStringOptional = Arrays.stream(split)
                .map(x -> x.split(";")[0] + "; ")
                .reduce((x, y) -> x + y);
        return cookieStringOptional.get() + "]";
    }

    private void validateParameter(Object param, Class<?> parameterType) {
        // 创建一个 Validator 对象
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        // 对参数对象进行校验
        Set<ConstraintViolation<Object>> violations = validator.validate(param);
        // 如果存在校验错误，则直接抛出异常
        if (!violations.isEmpty()) {
            throw new RuntimeException("Field validation failed, failed reason: " + parameterType.toString() + "." +  violations.iterator().next().getPropertyPath() + " " + violations.iterator().next().getMessage());
        }
    }


}
